import requests
from bs4 import BeautifulSoup
import pandas as pd
import time
import logging

# 配置日志
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

class SinaFinanceCrawler:
    """
    新浪财经历史分红数据爬虫类
    用于从新浪财经网站爬取历史分红数据并保存为Excel文件
    """
    
    def __init__(self, start_page=1, end_page=100, output_file='新浪财经历史分红数据.xlsx'):
        """
        初始化爬虫
        
        参数:
            start_page: 开始爬取的页码
            end_page: 结束爬取的页码
            output_file: 输出的Excel文件名
        """
        self.start_page = start_page
        self.end_page = end_page
        self.output_file = output_file
        # 使用用户提供的正确URL
        self.base_url = 'https://vip.stock.finance.sina.com.cn/q/go.php/vInvestConsult/kind/lsfh/index.phtml?p='
        self.headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36',
            'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
            'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8'
        }
    
    def crawl_page(self, page):
        """
        爬取指定页码的数据
        
        参数:
            page: 要爬取的页码
            
        返回:
            包含当前页数据的DataFrame
        """
        try:
            # 构造URL
            url = f'{self.base_url}{page}'
            logging.info(f'正在爬取第{page}页，URL: {url}')
            
            # 发送请求
            response = requests.get(url, headers=self.headers, timeout=15)
            response.encoding = 'gb2312'  # 新浪财经使用gb2312编码
            
            # 检查响应状态
            if response.status_code != 200:
                logging.error(f'爬取第{page}页失败，状态码: {response.status_code}')
                return None
            
            # 解析HTML
            soup = BeautifulSoup(response.text, 'html.parser')
            
            # 查找表格 - 从网页内容分析，表格应该是直接包含在body中的
            tables = soup.find_all('table')
            logging.info(f'在第{page}页找到{len(tables)}个表格')
            
            # 显示页面标题用于调试
            title = soup.title.text if soup.title else '无标题'
            logging.info(f'页面标题: {title}')
            
            # 尝试查找包含分红数据的表格
            data_table = None
            for table in tables:
                # 检查表格是否包含分红相关的关键字段
                rows = table.find_all('tr')
                if len(rows) > 5:
                    # 检查第一行是否包含预期的表头
                    first_row_text = ''.join([cell.text.strip() for cell in rows[0].find_all(['th', 'td'])])
                    if '代码' in first_row_text and '名称' in first_row_text and '累计股息' in first_row_text:
                        data_table = table
                        logging.info(f'找到包含分红数据的表格')
                        break
            
            if not data_table:
                logging.error(f'在第{page}页未找到包含分红数据的表格')
                # 保存部分HTML用于调试
                with open(f'page_{page}_debug.html', 'w', encoding='utf-8') as f:
                    f.write(response.text[:2000])  # 只保存前2000字符
                return None
            
            # 提取表头
            headers = []
            header_row = data_table.find('tr')
            if header_row:
                for cell in header_row.find_all(['th', 'td']):
                    header_text = cell.text.strip()
                    if header_text:  # 跳过空表头
                        headers.append(header_text)
            
            # 提取数据
            data = []
            rows = data_table.find_all('tr')[1:]  # 跳过表头行
            for row in rows:
                row_data = []
                for cell in row.find_all(['td']):
                    cell_text = cell.text.strip()
                    row_data.append(cell_text)
                if row_data and len(row_data) > 1:  # 确保行有数据且非空行
                    data.append(row_data)
            
            # 创建DataFrame
            if data and headers:
                # 处理列数不匹配的情况
                if len(headers) > len(data[0]):
                    headers = headers[:len(data[0])]
                elif len(data[0]) > len(headers):
                    headers.extend([f'列{i}' for i in range(len(headers), len(data[0]))])
                
                df = pd.DataFrame(data, columns=headers)
                logging.info(f'成功爬取第{page}页，共{len(df)}条数据')
                return df
            else:
                logging.warning(f'第{page}页没有找到有效数据')
                return None
        except Exception as e:
            logging.error(f'爬取第{page}页时发生错误: {str(e)}')
            import traceback
            traceback.print_exc()
            return None
    
    def crawl_all_pages(self, pages=None):
        """
        爬取所有指定页码的数据并合并
        
        参数:
            pages: 要爬取的页码范围，如果为None则使用初始化时设置的范围
            
        返回:
            合并后的DataFrame
        """
        all_data = []
        
        # 如果没有指定页码范围，使用初始化时设置的范围
        if pages is None:
            pages = range(self.start_page, self.end_page + 1)
        
        for page in pages:
            df = self.crawl_page(page)
            if df is not None and not df.empty:
                all_data.append(df)
            
            # 添加延迟，避免请求过于频繁
            if page < max(pages):
                time.sleep(2)
        
        if all_data:
            combined_df = pd.concat(all_data, ignore_index=True)
            logging.info(f'成功爬取所有页面，共{len(combined_df)}条数据')
            return combined_df
        else:
            logging.warning('未能爬取到任何数据')
            return pd.DataFrame()
    
    def save_to_excel(self, df, output_file=None):
        """
        将数据保存为Excel文件
        
        参数:
            df: 要保存的DataFrame
            output_file: 输出的Excel文件名，如果为None则使用初始化时设置的文件名
        """
        try:
            file_name = output_file if output_file else self.output_file
            df.to_excel(file_name, index=False)
            logging.info(f'数据已成功保存到{file_name}')
        except Exception as e:
            logging.error(f'保存数据到Excel时发生错误: {str(e)}')
    
    def run(self, pages=None, output_file=None):
        """
        运行爬虫的主方法
        
        参数:
            pages: 要爬取的页码范围，如果为None则使用初始化时设置的范围
            output_file: 输出的Excel文件名，如果为None则使用初始化时设置的文件名
        """
        logging.info('开始爬取新浪财经历史分红数据')
        start_time = time.time()
        
        # 爬取所有页面
        df = self.crawl_all_pages(pages)
        
        # 保存数据
        if not df.empty:
            self.save_to_excel(df, output_file)
        
        end_time = time.time()
        logging.info(f'爬取完成，共耗时{end_time - start_time:.2f}秒')

if __name__ == '__main__':
    # 创建爬虫实例并运行，爬取1-100页
    crawler = SinaFinanceCrawler()
    crawler.run()